package cn.com.libertymutual.core.xml;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.io.Closeables;

/**
 * XmlBinder封装了对JAXB的调用。提供了XML/Java对象的相互转换和验证。
 * 
 * 如果需要验证，那么应该提供schema
 * 
 * 
 */
public class XmlBinder<T> {

    public JAXBContext context;

    private Schema schema;

    private Document doc;
    
    /**
     * @update 2016-05-19 16:44 zhaoyu
     * 
     * @throws Exception
     * */
    public Document getDocument( File file ) throws Exception {
    	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		db = dbf.newDocumentBuilder();
		doc = db.parse(file);
		return doc;
    }
    public Document getDocument( String xmlStr ) throws Exception {
    	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db;
		db = dbf.newDocumentBuilder();
		InputStream   in_withcode   =   new   ByteArrayInputStream(xmlStr.getBytes("UTF-8"));  
		doc = db.parse(in_withcode);
		return doc;
    }

    /**
     * @update 2016-05-19 16:44 zhaoyu
     * 
     * @throws Exception
     * */
	public T getElementObject( String xpath, Class<T> t ) throws Exception{
    	XPathFactory xpathFactory = XPathFactory.newInstance();
    	XPath path = xpathFactory.newXPath();

		Object obj = path.evaluate(xpath, doc, XPathConstants.NODE );
		if( obj == null ) return null;

		return (T) ( getUnmarshaller().unmarshal((Element)obj, t).getValue() );

    }
	
    /**
     * @update 2016-05-19 16:44 zhaoyu
     * 
     * @throws Exception
     * */

	public List<T> getElementList( String xpath, Class<T> t ) throws Exception {
    	XPathFactory xpathFactory = XPathFactory.newInstance();
    	XPath path = xpathFactory.newXPath();

			Object obj = path.evaluate(xpath, doc, XPathConstants.NODESET );
			if( obj == null ) return null;
			NodeList node = (NodeList) obj;
			
			List<T> list = Lists.newArrayList();
			for( int i=0, n=node.getLength(); i<n; i++ ) {
				//Element element = (Element) node.item(i);
				list.add( (T) ( getUnmarshaller().unmarshal(node.item(i), t).getValue() ) );
			}
			
			return list;
    }
	
    /**
     * 构造一个指定上下文路径的XmlBinder
     * 
     * @param contextPath
     * @throws JAXBException 
     * 
     * 
     */
    public XmlBinder(String contextPath, ClassLoader classLoader) throws JAXBException {
                 context = JAXBContext.newInstance(contextPath, classLoader);
     
    }

    /**
     * 构造一个指定上下文路径及Schema的XmlBinder
     * 
     * @param contextPath
     * @param url
     * @throws JAXBException 
     * @throws SAXException 
     */
    public XmlBinder(String contextPath, ClassLoader classLoader, URL url) throws JAXBException, SAXException {
        this(contextPath, classLoader);
        schema = createSchema(url);
    }

    /**
     * 根据一个类定义构造一个简单的XmlBinder
     * 
     * @param classToBeBound
     * @throws JAXBException 
     */
    public XmlBinder(Class<T> classToBeBound) throws JAXBException {
        context = JAXBContext.newInstance(classToBeBound);
 
    }

    /**
     * 根据一个类定义和schema构造一个简单的XmlBinder
     * 
     * @param classToBeBound
     * @param url
     * @throws JAXBException 
     * @throws SAXException 
     */
    public XmlBinder(Class<T> classToBeBound, URL url) throws JAXBException, SAXException {
        this(classToBeBound);
        schema = createSchema(url);
    }

    /**
     * 获得XML编组器
     * 
     * @return
     * @throws JAXBException
     */
    protected Marshaller getMarshaller() throws JAXBException {

        return context.createMarshaller();
    }

    /**
     * 获得XML解码器
     * 
     * @return
     * @throws JAXBException
     */
    protected Unmarshaller getUnmarshaller() throws JAXBException {
        Unmarshaller unmarshaller = context.createUnmarshaller();
        if (schema != null)
            unmarshaller.setSchema(schema);
        return unmarshaller;
    }

    /**
     * 从URL获得一个schema对象
     * 
     * @param url
     * @return
     * @throws SAXException 
     */
    private static Schema createSchema(URL url) throws SAXException {
        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        return schemaFactory.newSchema(url);  
    }

    /**
     * 编组一个对象到输出流中
     * 
     * @param object
     * @param os
     * @throws Exception
     */
    public void marshal(T object, OutputStream os) throws Exception {
        marshal(object, os, false);
    }
    
    public void marshal(T object, OutputStream os, boolean isFormat ) throws Exception {
    	Marshaller m = getMarshaller();
        if( isFormat ){
        	m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormat);
            m.marshal(object, os); 
        }
    }

    /**
     * 编组一个对象到字节数组中
     * 
     * @param object
     * @return
     * @throws Exception
     */
    public byte[] marshal(T object) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        marshal(object, baos);
        try {
        	return baos.toByteArray();
        }
        finally {
        	baos.close();
        }
    }

    /**
     * 编组一个对象到字符串
     * 
     * @param object
     * @return
     * @throws JAXBException 
     * @throws Exception
     */
    public String marshalString(T object) throws JAXBException {
    	return marshalString(object, null, false);
    }
    

    public String marshalString(T object, boolean isFormat ) throws JAXBException {
    	return marshalString(object, null, isFormat);
    }
    
    public String marshalString(T object, String encoding) throws JAXBException {
        return marshalString(object, encoding, false);
    }

    public String marshalString(T object, String encoding, boolean isFormat) throws JAXBException {
        StringWriter writer = new StringWriter();
        Marshaller m = getMarshaller();
        if( !Strings.isNullOrEmpty(encoding) )
        	m.setProperty(Marshaller.JAXB_ENCODING, encoding);
        if( isFormat )
        	m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormat);
        
        m.marshal(object, writer);
        try {
        	return writer.toString();
        }
        finally {
        	try {
				writer.close();
			} catch (IOException e) {
				System.err.println(e.getMessage());
			}
        }
    }
    
    /**
     * 将xml转换为对象
     * 
     * @param is
     * @param autoClose
     * @return
     * @throws JAXBException
     */
    @SuppressWarnings("unchecked")
    public T unmarshal(InputStream is, boolean autoClose) throws JAXBException {
        try {
            return (T) getUnmarshaller().unmarshal(is);
        } finally {
            if (autoClose)
                Closeables.closeQuietly(is);
        }
    }
    

    @SuppressWarnings("unchecked")
	public T unmarshal2(InputStream is, boolean autoClose) throws Exception {
        try {
        	SAXParserFactory sax = SAXParserFactory.newInstance();
        	sax.setNamespaceAware(false);
        	XMLReader r = sax.newSAXParser().getXMLReader();
        	Source source = new SAXSource(r, new InputSource(is));
        	
        	return (T) getUnmarshaller().unmarshal(source);
            //return (T) getUnmarshaller().unmarshal(is);
   		} finally {
            if (autoClose)
                Closeables.closeQuietly(is);
        }
        
    }

    public T unmarshal(File file) throws JAXBException, FileNotFoundException {
        FileInputStream is = new FileInputStream(file);
        return unmarshal(is, true);
    }

    /**
     * 将xml转换为对象
     * 
     * @param xml
     * @return
     * @throws JAXBException
     */
    @SuppressWarnings("unchecked")
    public T unmarshal(String xml) throws JAXBException {
        Reader reader = new StringReader(xml);
        try {
        	return (T) getUnmarshaller().unmarshal(reader);
        }
        finally {
        	try {
				reader.close();
			} catch (IOException e) {
				System.err.println(e.getMessage());
			}
        }
    }

}
